package org.dreamwork.cai.io.impl;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.dreamwork.cai.io.IRemoteServiceStub;
import org.dreamwork.cai.internal.pojo.JsonInvocationPayload;
import org.dreamwork.cai.internal.pojo.JsonInvocationResult;
import org.dreamwork.cai.srf.MethodInfo;
import org.dreamwork.cai.srf.ServiceRouteItem;
import org.dreamwork.gson.GsonHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.rmi.RemoteException;

public class SimpleJsonRemoteServiceStubImpl implements IRemoteServiceStub {
    private final Logger logger = LoggerFactory.getLogger (SimpleJsonRemoteServiceStubImpl.class);

    @Override
    public Object invoke (ServiceRouteItem sri, MethodInfo info, Object... args) throws RemoteException {
        Gson g = GsonHelper.getGson (false, true);
        JsonInvocationPayload payload = new JsonInvocationPayload ();
        if (args != null && args.length > 0) {
            payload.params = g.toJson (args);
        }
        payload.name = sri.name;
        payload.uuid = info.uuid;
        payload.namespace = sri.frame.namespace;

        if (logger.isTraceEnabled ()) {
            logger.trace ("invoking remote service with uri: {}:{}", sri.frame.host, sri.frame.port);
            Gson p = new GsonBuilder ().setPrettyPrinting ().create ();
            logger.trace ("remote call request:\r\n{}", p.toJson (payload));
        }

        try (Socket socket = new Socket ()) {
            socket.setSoTimeout (10000); // 设置超时 10s
            InetAddress address = InetAddress.getByName (sri.frame.host);
            SocketAddress sa = new InetSocketAddress (address, sri.frame.port);
            socket.connect (sa, 10000);
            InputStream in = socket.getInputStream ();
            OutputStream out = socket.getOutputStream ();

            // 发送调用请求
            ObjectOutputStream oos = new ObjectOutputStream (out);
            String content = g.toJson (payload);
            byte[] buff = content.getBytes (StandardCharsets.UTF_8);
            oos.writeShort (buff.length);
            oos.write (buff);
            oos.flush ();
            out.flush ();

            // 接收调用结果
            ByteArrayOutputStream baos = new ByteArrayOutputStream (8192);
            int length;
            buff = new byte[1024];
            try {
                while ((length = in.read (buff)) != -1) {
                    baos.write (buff, 0, length);
                    baos.flush ();
                }
            } catch (Throwable ex) {
                ex.printStackTrace ();
            }

            // 解析调用结果
            buff = baos.toByteArray ();
            length = ((buff[0] & 0xff) << 8) | (buff[1] & 0xff);
            int remain = buff.length - 2;
            if (length != remain) {
                throw new IOException ("expect " + length + " bytes, but received " + remain + " bytes");
            }
            content = new String (buff, 2, length, StandardCharsets.UTF_8);
            if (logger.isTraceEnabled ()) {
                logger.trace ("**************** raw result ****************");
                logger.trace (content);
                logger.trace ("********************************************");
            }
            JsonInvocationResult result = g.fromJson (content, JsonInvocationResult.class);
            if (result != null && result.code == 0 && info.returnType != void.class && info.returnType != Void.class) { // 调用成功
                return g.fromJson (result.result, info.returnType);
            }
        } catch (Exception ex) {
            logger.warn (ex.getMessage (), ex);
            throw new RemoteException (ex.getMessage (), ex);
        }
        return null;
    }
}